const {ResposeCode, GlobalPaths} = require("./basic");
const IOSystem = require("./io_system");
const Utils = require("./utils");
const Log = require("./logger");
const {Node, UniqueTree} = require("./server-tree");

const _summaryFileURL = GlobalPaths.ROOT_CONTENT + ".summary.json";

/**
 * sample of summary
metas:[
    {
        "PID": "S-7aa05caa-a83a-41a9-91b5-10f27e683150",
        "createTime": "2021-12-17T00:00:00.000Z",
        "modifyTime": "2021-12-17T00:00:00.000Z",
        "name": "FF",
        "published": false,
        "brief": '',
        "toc": false,
        "tags": [ "tag1", "tag2" ],
        "children": [PIDa,PIDb]
    }
]
*/
const _templateSummary = Object.freeze({
    metas:[],
    version: {major: 0, minor: 4, patch: 0}
});

/**
 * "indexes": [
 *      "S-76f66a26-0ce5-41a8-8005-75a0465c3297",
 *      "S-24bbd1ad-572b-4462-beea-eff05f08e5f6",
 *      "S-615aa0e1-dd0c-47cc-95f3-3f286492edc7"
 * ],
 * sections:{
 *      "S-6a33039c-dbdf-455c-947e-79f22cbe6fbb": {
 *          "WID": "S-6a33039c-dbdf-455c-947e-79f22cbe6fbb", 
 *          "type": 8, 
 *          "data": any 
 *      },
 * }
 */
const _templateConfig = Object.freeze({
    indexes: [],
    sections: {},
    version: {major: 0, minor: 4, patch: 0}
});





function _GetChildrenTransferJsonObject(parentNode, includeChildren) {
    const _children = [];
    let _node = parentNode.firstChildNode;
    while(_node) {
        _children.push(_node.GetTransferJsonObject(includeChildren));
        _node = _node.nextNode;
    }
    return _children;
}

class RootNode extends Node {
    constructor() {
        super();
        Utils.CreateProperty(this, 'PID', "__ROOT_NODE__", false, true, false);
    }
    GetTransferJsonObject(includeChildren) {
        return _GetChildrenTransferJsonObject(this, includeChildren);
    }
}

class MetaData extends Node {
    constructor(obj) {
        super();
        Utils.CreateProperty(this, 'PID', obj.PID, false, true, false);
        this.PSUID = obj.PSUID;
        this.name = obj.name ?? "__default_name__";
        this.createTime = obj.createTime ? new Date(obj.createTime) : new Date();
        this.modifyTime = obj.modifyTime ? new Date(obj.modifyTime) : new Date();
        this.brief = obj.brief ?? "";
        this.published = obj.published ?? false;
        this.toc = obj.toc ?? true;
        this.tags = obj.tags ?? [];
        this.bannerURL = obj.bannerURL ?? '';
    }
    GetTransferJsonObject(includeChildren) {
        const _out = {
            PID: this.PID,
            PSUID: this.PSUID,
            createTime: this.createTime.toISOString(),
            modifyTime: this.modifyTime.toISOString(),
            name: this.name,
            published: this.published,
            brief: this.brief,
            toc: this.toc,
            tags: this.tags,
            bannerURL: this.bannerURL,
            children: undefined,
        };
        if(includeChildren) {
            _out.children = _GetChildrenTransferJsonObject(this, includeChildren);
        }
        return _out;
    }
}

function _PageHandler(PID) {
    Log.Info(`create page handler to deal with PID:${PID}`);
    const _dirPath = `${GlobalPaths.ROOT_CONTENT}${PID}/`;
    IOSystem.CreateFolderIfNotExist(_dirPath);
    const _configFileURL = `${_dirPath}config.json`;

    const _GetConfigString = function () {
        const _configString = IOSystem.FileExist(_configFileURL) ? IOSystem.ReadFileUTF8(_configFileURL) : JSON.stringify(_templateConfig);
        ///Log.Debug(`[debug] _GetConfigString(), return:\n${_configString}`);
        return _configString;
    }
    const _GetConfigJson = function () {
        const _str = _GetConfigString();
        return JSON.parse(_str);
    }
    /**
     * configString + content;
     */
    const _GetPageJson = function () {
        const _config = _GetConfigJson();
        ///Log.Debug(`[debug] _GetPageJson(),config:\n${JSON.stringify(_config)}`);
        const _widgets = _config.sections;
        const _arr = _config.indexes;
        const _N = _arr.length;
        for(let i = 0; i < _N; ++i) {
            const _WID = _arr[i];
            const _value = IOSystem.ReadFileUTF8(`${_dirPath}${_WID}`);
            _widgets[_WID].data = _value;
        }
        return _config;
    }

    const _GetPageString = function () {
        const _value = _GetPageJson();
        return JSON.stringify(_value);
    }

    const _SetConfigJson = function (value) {
        IOSystem.WriteFileUTF8(_configFileURL, JSON.stringify(value));
    }

    const _SetWidgetString = function (WID, str) {
        ///Log.Info(`_SetWidgetString(),WID:${WID},str:${str}`);
        IOSystem.WriteFileUTF8(`${_dirPath}${WID}`, str);
    }

    const _SetWidgetJson = function (WID, value) {
        IOSystem.WriteFileUTF8(`${_dirPath}${WID}`, JSON.stringify(value));
    }

    const _DelWidget = function (WID) {
        IOSystem.DeleteFile(`${_dirPath}${WID}`);
    }

    const _Del = function () {
        IOSystem.DeleteFolder(_dirPath);
    }

    return {
        GetConfigString: _GetConfigString,
        GetConfigJson: _GetConfigJson,
        GetPageString: _GetPageString,
        GetPageJson: _GetPageJson,

        SetConfigJson: _SetConfigJson,
        SetWidgetJson: _SetWidgetJson,
        SetWidgetString: _SetWidgetString,

        DelWidget: _DelWidget,
        Delete: _Del,
    }
}

const _rootNode = new RootNode();
const _uniqueTree = new UniqueTree('PID', _rootNode);

function _SaveSummary() {
    const _out = JSON.parse(JSON.stringify(_templateSummary));
    _out.metas = _rootNode.GetTransferJsonObject(true);
    IOSystem.WriteFileUTF8(_summaryFileURL, JSON.stringify(_out));
}

function GetSummary() {
    const _out = JSON.parse(JSON.stringify(_templateSummary));
    _out.metas = _rootNode.GetTransferJsonObject(true);
    return _out;
}

function AddMetaData(value) {
    const _PID = value.PID;
    let _meta = _uniqueTree.GetNodeByKey(_PID);
    if(_meta) {
        return ResposeCode.META_ALREADY_EXIST;
    } else {
        _meta = new MetaData(value);
        const _pMeta = _uniqueTree.GetNodeByKey(value.PPID) ?? _rootNode;
        _uniqueTree.InsertAIntoB(_meta, _pMeta, 0);
        _SaveSummary();
        return ResposeCode.OK;
    }
}

function UpdateMetaData(value) {
    const _meta = _uniqueTree.GetNodeByKey(value.PID);
    if(_meta) {
        _meta.name = value.name ?? "__default_name__";
        _meta.createTime = new Date(value.createTime);
        _meta.modifyTime = new Date(value.modifyTime);
        _meta.brief = value.brief ?? "";
        _meta.published = value.published ?? false;
        _meta.toc = value.toc ?? true;
        _meta.tags = value.tags ?? [];
        _meta.bannerURL = value.bannerURL ?? '';
        _SaveSummary();
        return ResposeCode.OK;
    } else {
        return ResposeCode.META_NOT_EXIST;
    }
}

function DeletePage(PID) {
    const _meta = _uniqueTree.GetNodeByKey(PID);
    if (_meta) {
        if (_uniqueTree.RemoveNode(_meta)) {
            _SaveSummary();

            const _h = _PageHandler(PID);
            _h.Delete();
            return ResposeCode.OK;
        }
    }
    return ResposeCode.META_NOT_EXIST;
}

function GetArticleData(PID) {
    const _h = _PageHandler(PID);
    return _h.GetPageString();
}

function AddSectionData(PID, index, sectionData) {
    ///Log.Info(`AddSectionData(),PID:${PID},index:${index},sectionData:${JSON.stringify(sectionData)}`);
    const _h = _PageHandler(PID);
    const _config = _h.GetConfigJson();
    const _metas = _config.sections;
    if(_metas[sectionData.WID]) {
        return {code: ResposeCode.SECTION_ALREADY_EXIST};
    } else {
        //// save index;
        const _indexes = _config.indexes;
        index = Utils.Clamp(0, _indexes.length, index);
        _indexes.splice(index, 0, sectionData.WID);
        _metas[sectionData.WID] = {
            WID: sectionData.WID,
            type: sectionData.type,
        };
        _h.SetConfigJson(_config);
        return {code: ResposeCode.OK};
    }
}

function MoveSectionData(PID, WID, index) {
    const _h = _PageHandler(PID);
    const _config = _h.GetConfigJson();
    const _indexes = _config.indexes;
    const _oldIndex = _indexes.indexOf(WID);
    if(_oldIndex >= 0) {
        _indexes.splice(_oldIndex, 1);
        index = Utils.Clamp(0, _indexes.length, index);
        _indexes.splice(index, 0, WID);
        _h.SetConfigJson(_config);
        return ResposeCode.OK;
    } else {
        return ResposeCode.SECTION_NOT_EXIST;
    }
}

function UpdateSectionData(PID, sectionData) {
    const _h = _PageHandler(PID);
    const _config = _h.GetConfigJson();
    const _indexes = _config.indexes;
    const _idx = _indexes.indexOf(sectionData.WID);
    if(_idx >= 0) {
        _h.SetWidgetString(sectionData.WID, sectionData.data);
        return {code: ResposeCode.OK};
    } else {
        return {code: ResposeCode.SECTION_NOT_EXIST, msg: 'section NOT exist.'};
    }
}

function DeleteSectionData(PID, WID) {
    ///Log.Info(`DeleteSectionData(),PID:${PID},WID:${WID}`);
    const _h = _PageHandler(PID);
    const _config = _h.GetConfigJson();
    const _metas = _config.sections;
    if (_metas[WID]) {
        delete _metas[WID];
    } else {
        return ResposeCode.SECTION_NOT_EXIST;
    }
    const _indexes = _config.indexes;
    const _idx = _indexes.indexOf(WID);
    if(_idx >= 0) {
        _indexes.splice(_idx, 1);
    } else {
        return ResposeCode.SECTION_NOT_EXIST;
    }
    _h.SetConfigJson(_config);

    /// delete widget;
    _h.DelWidget(WID);

    return ResposeCode.OK;
}

(function () {
    let _configObject = undefined;
    if(IOSystem.FileExist(_summaryFileURL)) {
        _configObject = JSON.parse(IOSystem.ReadFileUTF8(_summaryFileURL));
    } else {
        _configObject = JSON.parse(JSON.stringify(_templateSummary));
    }
    if(Utils.CheckInvalid(_configObject)) {
        Log.Error("can NOT read '.summary.json' file from disk!");
        return;
    }

    let _InsertToTree = function (parentNode, arrChild) {
        for(let i = arrChild.length - 1; i >= 0; --i) {
            const _obj = arrChild[i];
            const _meta = new MetaData(_obj);
            _uniqueTree.InsertAIntoB(_meta, parentNode, 0);
            _InsertToTree(_meta, _obj.children);
        }
    }
    _InsertToTree(_rootNode, _configObject.metas);
})()

module.exports = Object.freeze({
    GetSummary,
    AddMetaData,
    UpdateMetaData,
    DeletePage,

    GetArticleData,
    AddSectionData,
    UpdateSectionData,
    MoveSectionData,
    DeleteSectionData,
});
